/** 
 * Copyright (C) 2018 Jeebiz (http://jeebiz.net).
 * All Rights Reserved. 
 */
package net.jeebiz.cloud.extras.filestore.setup.provider;


import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.biz.utils.FilenameUtils;
import org.springframework.security.boot.biz.userdetails.SecurityPrincipal;
import org.springframework.security.boot.utils.SubjectUtils;
import org.springframework.web.multipart.MultipartFile;

import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.github.tobato.fastdfs.spring.boot.FastdfsTemplate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import net.jeebiz.cloud.api.exception.BizRuntimeException;
import net.jeebiz.cloud.api.utils.CollectionUtils;
import net.jeebiz.cloud.extras.core.web.vo.FileMetaDataVo;
import net.jeebiz.cloud.extras.core.web.vo.FilestoreVo;
import net.jeebiz.cloud.extras.filestore.dao.IFilestoreDao;
import net.jeebiz.cloud.extras.filestore.dao.entities.FilestoreModel;
import net.jeebiz.cloud.extras.filestore.setup.Constants;
import net.jeebiz.cloud.extras.filestore.web.vo.FilestoreDownloadVo;


public class FastdfsFilestoreProvider implements FilestoreProvider {
	
	private static final String TO = "fdfs";
	
	private DownloadByteArray callback = new DownloadByteArray();
	
	private IFilestoreDao filestoreDao;
	private FastFileStorageClient fdfsStorageClient;
	private FastdfsTemplate fdfsTemplate;
	
	public FastdfsFilestoreProvider(IFilestoreDao filestoreDao, FastFileStorageClient fdfsStorageClient,
			FastdfsTemplate fdfsTemplate) {
		this.filestoreDao = filestoreDao;
		this.fdfsStorageClient = fdfsStorageClient;
		this.fdfsTemplate = fdfsTemplate;
	}

	@Override
	public String getName() {
		return TO;
	}
	 
	@Override
	public FilestoreVo upload(MultipartFile file) throws Exception {
		FilestoreModel model = null;
		try {
			
			SecurityPrincipal principal = SubjectUtils.getPrincipal(SecurityPrincipal.class);
			
			// 上传文件
			String uuid = UUID.randomUUID().toString();
			StorePath storePath = getFdfsStorageClient().uploadFile(Constants.GROUP_NAME, file.getInputStream(),
                    file.getSize(), uuid + FilenameUtils.getFullExtension(file.getOriginalFilename()));
			// 存储文件的元信息
			try {
				Set<MetaData> metaDataSet = Sets.newHashSet();
				Metadata metadata = ImageMetadataReader.readMetadata(file.getInputStream());
				for (Directory directory : metadata.getDirectories()) {
				    for (Tag tag : directory.getTags()) {
				        //格式化输出[directory.getName()] - tag.getTagName() = tag.getDescription()
				        System.out.format("[%s] - %s = %s\n",  directory.getName(), tag.getTagName(), tag.getDescription());
				        metaDataSet.add(new MetaData(directory.getName(), tag.getTagName()));
				    }
				    if (directory.hasErrors()) {
				        for (String error : directory.getErrors()) {
				            System.err.format("ERROR: %s", error);
				        }
				    }
				}
				getFdfsStorageClient().mergeMetadata(storePath.getGroup(),  storePath.getPath(), metaDataSet);
			} catch (Exception e) {
			}
			// 文件存储记录对象
			model = new FilestoreModel();
			
			model.setUuid(uuid);
			model.setUid(principal.getUserkey());
			model.setName(file.getOriginalFilename());
			model.setExt(FilenameUtils.getExtension(file.getOriginalFilename()));
			model.setTo(TO);
			model.setGroup(storePath.getGroup());
			model.setPath(storePath.getPath());
			getFilestoreDao().insert(model);
			
			// 文件存储信息
			FilestoreVo attVo = new FilestoreVo();
			attVo.setUuid(uuid);
			attVo.setName(file.getOriginalFilename());
			attVo.setPath(storePath.getPath());
			attVo.setUrl(getFdfsTemplate().getAccsssURL(storePath));
			
			return attVo;

		} catch (Exception e) {
			try {
				if (model != null) {
					getFdfsStorageClient().deleteFile(model.getGroup(), model.getPath());
				}
			} catch (Exception e1) {
				// 忽略删除异常
			}
			throw e;
		}
	}
	
	@Override
	public List<FilestoreVo> upload(MultipartFile[] files) throws Exception {
		
		List<FilestoreModel> modelList = Lists.newArrayList();
		List<FilestoreVo> attList = Lists.newArrayList();
		try {
			
			SecurityPrincipal principal = SubjectUtils.getPrincipal(SecurityPrincipal.class);
			
			for (MultipartFile file : files) {
				
				// 上传文件
				String uuid = UUID.randomUUID().toString();
				StorePath storePath = getFdfsStorageClient().uploadFile(Constants.GROUP_NAME, file.getInputStream(),
						file.getSize(), file.getOriginalFilename());
				// 存储文件的元信息
				try {
					Set<MetaData> metaDataSet = Sets.newHashSet();
					Metadata metadata = ImageMetadataReader.readMetadata(file.getInputStream());
					for (Directory directory : metadata.getDirectories()) {
					    for (Tag tag : directory.getTags()) {
					        //格式化输出[directory.getName()] - tag.getTagName() = tag.getDescription()
					        System.out.format("[%s] - %s = %s\n",  directory.getName(), tag.getTagName(), tag.getDescription());
					        metaDataSet.add(new MetaData(directory.getName(), tag.getTagName()));
					    }
					    if (directory.hasErrors()) {
					        for (String error : directory.getErrors()) {
					            System.err.format("ERROR: %s", error);
					        }
					    }
					}
					getFdfsStorageClient().mergeMetadata(storePath.getGroup(),  storePath.getPath(), metaDataSet);
				} catch (Exception e) {
				}
				

				// 文件存储记录对象
				FilestoreModel model = new FilestoreModel();
				
				model.setUuid(uuid);
				model.setUid(principal.getUserkey());
				model.setName(file.getOriginalFilename());
				model.setExt(FilenameUtils.getExtension(file.getOriginalFilename()));
				model.setTo(TO);
				model.setGroup(storePath.getGroup());
				model.setPath(storePath.getPath());
				getFilestoreDao().insert(model);
				modelList.add(model);
				
				// 文件存储信息
				FilestoreVo attVo = new FilestoreVo();
				attVo.setUuid(uuid);
				attVo.setName(file.getOriginalFilename());
				attVo.setPath(storePath.getPath());
				attVo.setUrl(getFdfsTemplate().getAccsssURL(storePath));
				
				attList.add(attVo);

			} 
		} catch (Exception e) {
			try {
				for (FilestoreModel model : modelList) {
					getFdfsStorageClient().deleteFile(model.getGroup(), model.getPath());
				}
			} catch (Exception e1) {
				// 忽略删除异常
			}
			throw e;
		}
		return attList;
	}

	@Override
	public boolean deleteByPath(List<String> paths) throws Exception {
		
		if(CollectionUtils.isEmpty(paths)) {
			return false;
		}
		// 查询UID对象的文件记录
		List<FilestoreModel> files = getFilestoreDao().getPaths(paths);
		// 删除文件记录
		getFilestoreDao().deleteByPaths(paths);
		// 删除服务器文件，如果出现异常将会回滚前面的操作
		for (FilestoreModel model : files) {
			// 删除旧的文件
			getFdfsStorageClient().deleteFile(model.getGroup(), model.getPath());
			getFilestoreDao().delete(model.getUuid());
		}
		
		return true;
	}
	
	@Override
	public boolean deleteByUuid(List<String> uuids) throws Exception {
		
		if(CollectionUtils.isEmpty(uuids)) {
			return false;
		}
		// 查询UID对象的文件记录
		List<FilestoreModel> files = getFilestoreDao().getFiles(uuids);
		// 删除文件记录
		getFilestoreDao().deleteByUuids(uuids);
		// 删除服务器文件，如果出现异常将会回滚前面的操作
		for (FilestoreModel model : files) {
			// 删除旧的文件
			getFdfsStorageClient().deleteFile(model.getGroup(), model.getPath());
			getFilestoreDao().delete(model.getUuid());
		}
		
		return true;
	}


	@Override
	public FilestoreVo reupload(String uuid, MultipartFile file) throws Exception {
		
		// 查询文件信息
		FilestoreModel model = getFilestoreDao().getByUuid(uuid);
		if(model == null) {
			throw new BizRuntimeException(uuid + "指向的文件不存在");
		}
		
		SecurityPrincipal principal = SubjectUtils.getPrincipal(SecurityPrincipal.class);
		
		// 上传新文件	
		StorePath storePath = getFdfsStorageClient().uploadFile(model.getGroup(), file.getInputStream(), file.getSize(), file.getOriginalFilename());
		// 存储文件的元信息
		try {
			Set<MetaData> metaDataSet = Sets.newHashSet();
			Metadata metadata = ImageMetadataReader.readMetadata(file.getInputStream());
			for (Directory directory : metadata.getDirectories()) {
			    for (Tag tag : directory.getTags()) {
			        //格式化输出[directory.getName()] - tag.getTagName() = tag.getDescription()
			        System.out.format("[%s] - %s = %s\n",  directory.getName(), tag.getTagName(), tag.getDescription());
			        metaDataSet.add(new MetaData(directory.getName(), tag.getTagName()));
			    }
			    if (directory.hasErrors()) {
			        for (String error : directory.getErrors()) {
			            System.err.format("ERROR: %s", error);
			        }
			    }
			}
			getFdfsStorageClient().mergeMetadata(storePath.getGroup(),  storePath.getPath(), metaDataSet);
		} catch (Exception e) {
		}
		// 文件存储信息
		String uuid1 = UUID.randomUUID().toString();
		FilestoreVo attVo = new FilestoreVo();
		attVo.setUuid(uuid1);
		attVo.setName(file.getOriginalFilename());
		attVo.setPath(storePath.getPath());
		attVo.setUrl(getFdfsTemplate().getAccsssURL(storePath));

		// 文件存储记录对象
		model.setUuid(uuid1);
		model.setUid(principal.getUserkey());
		model.setName(file.getOriginalFilename());
		model.setExt(FilenameUtils.getExtension(file.getOriginalFilename()));
		model.setTo(TO);
		model.setGroup(storePath.getGroup());
		model.setPath(storePath.getPath());
		getFilestoreDao().insert(model);
		
		// 删除旧的文件
		getFdfsStorageClient().deleteFile(model.getGroup(), model.getPath());
		getFilestoreDao().delete(uuid);
		
		return attVo;
	}

	@Override
	public List<FilestoreVo> listByPath(List<String> paths) throws Exception {
		
		List<FilestoreVo> attList = Lists.newArrayList();
		
		for (String path : paths) {
			
			// 文件存储信息
			FilestoreVo attVo = new FilestoreVo();
			
			attVo.setPath(path);
			attVo.setUrl(getFdfsTemplate().getAccsssURL(Constants.GROUP_NAME, path));
			
			// 文件元数据
			try {
				Set<MetaData> metaDatas = getFdfsStorageClient().getMetadata(Constants.GROUP_NAME, path);
				if(!CollectionUtils.isEmpty(metaDatas)) {
					attVo.setMetadata(metaDatas.stream().map(m -> {
						FileMetaDataVo metaVo = new FileMetaDataVo();
						metaVo.setName(m.getName());
						metaVo.setValue(m.getValue());
						return metaVo; 
					}).collect(Collectors.toSet()));
				}
			} catch (Exception e) {
			}
			
			attList.add(attVo);

		} 
		
		return attList;
	}

	@Override
	public List<FilestoreVo> listByUuid(List<String> uuids) throws Exception {
		
		List<FilestoreVo> attList = Lists.newArrayList();
		
		// 查询文件信息
		List<FilestoreModel> fileList = getFilestoreDao().getFiles(uuids);
				
		for (FilestoreModel model : fileList) {
			
			// 文件存储信息
			FilestoreVo attVo = new FilestoreVo();
			
			attVo.setUuid(model.getUuid());
			attVo.setName(model.getName());
			attVo.setPath(model.getPath());
			attVo.setUrl(getFdfsTemplate().getAccsssURL(model.getGroup(), model.getPath()));
			
			// 文件元数据
			try {
				Set<MetaData> metaDatas = getFdfsStorageClient().getMetadata(model.getGroup(), model.getPath());
				if(!CollectionUtils.isEmpty(metaDatas)) {
					attVo.setMetadata(metaDatas.stream().map(m -> {
						FileMetaDataVo metaVo = new FileMetaDataVo();
						metaVo.setName(m.getName());
						metaVo.setValue(m.getValue());
						return metaVo; 
					}).collect(Collectors.toSet()));
				}
			} catch (Exception e) {
			}
			
			attList.add(attVo);

		} 
		
		return attList;
	}

	@Override
	public FilestoreDownloadVo downloadByPath(String path) throws Exception {
		
		// 查询文件信息
		FilestoreModel model = getFilestoreDao().getByPath(path);
		if(model == null) {
			throw new BizRuntimeException(path + "指向的文件不存在");
		}
		
		// 文件存储信息
		FilestoreDownloadVo attVo = new FilestoreDownloadVo();
		
		attVo.setUuid(model.getUuid());
		attVo.setName(model.getName());
		attVo.setPath(model.getPath());
		attVo.setUrl(getFdfsTemplate().getAccsssURL(model.getGroup(), model.getPath()));
		
		// 文件元数据
		try {
			Set<MetaData> metaDatas = getFdfsStorageClient().getMetadata(model.getGroup(), model.getPath());
			if(!CollectionUtils.isEmpty(metaDatas)) {
				attVo.setMetadata(metaDatas.stream().map(m -> {
					FileMetaDataVo metaVo = new FileMetaDataVo();
					metaVo.setName(m.getName());
					metaVo.setValue(m.getValue());
					return metaVo; 
				}).collect(Collectors.toSet()));
			}
		} catch (Exception e) {
		}
		
		byte[] bytes = getFdfsStorageClient().downloadFile(model.getGroup(), model.getPath(), callback);
		attVo.setBytes(bytes);
		  
		return attVo;
		
	}

	@Override
	public FilestoreDownloadVo downloadByUuid(String uuid) throws Exception {
		
		// 查询文件信息
		FilestoreModel model = getFilestoreDao().getByUuid(uuid);
		if(model == null) {
			throw new BizRuntimeException(uuid + "指向的文件不存在");
		}
		
		// 文件存储信息
		FilestoreDownloadVo attVo = new FilestoreDownloadVo();
		
		attVo.setUuid(model.getUuid());
		attVo.setName(model.getName());
		attVo.setPath(model.getPath());
		attVo.setUrl(getFdfsTemplate().getAccsssURL(model.getGroup(), model.getPath()));
		
		// 文件元数据
		try {
			Set<MetaData> metaDatas = getFdfsStorageClient().getMetadata(model.getGroup(), model.getPath());
			if(!CollectionUtils.isEmpty(metaDatas)) {
				attVo.setMetadata(metaDatas.stream().map(m -> {
					FileMetaDataVo metaVo = new FileMetaDataVo();
					metaVo.setName(m.getName());
					metaVo.setValue(m.getValue());
					return metaVo; 
				}).collect(Collectors.toSet()));
			}
		} catch (Exception e) {
		}
		
		byte[] bytes = getFdfsStorageClient().downloadFile(model.getGroup(), model.getPath(), callback);
		attVo.setBytes(bytes);
		  
		return attVo;
	}
	
	
	public IFilestoreDao getFilestoreDao() {
		return filestoreDao;
	}

	public FastFileStorageClient getFdfsStorageClient() {
		return fdfsStorageClient;
	}

	public FastdfsTemplate getFdfsTemplate() {
		return fdfsTemplate;
	}

	
}